-
Notifications
You must be signed in to change notification settings - Fork 404
Use correct to_remote script in counterparty commitments #2605
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use correct to_remote script in counterparty commitments #2605
Conversation
37f3469
to
3c1b187
Compare
Codecov ReportAttention:
❗ Your organization needs to install the Codecov GitHub app to enable full functionality. Additional details and impacted files@@ Coverage Diff @@
## main #2605 +/- ##
==========================================
- Coverage 89.03% 88.99% -0.04%
==========================================
Files 112 112
Lines 86351 86706 +355
Branches 86351 86706 +355
==========================================
+ Hits 76879 77167 +288
- Misses 7235 7308 +73
+ Partials 2237 2231 -6
☔ View full report in Codecov by Sentry. |
f6b21c9
to
1fb2a41
Compare
The commit titled |
It is, check again :) We previously had an estimate of 108, now 109. |
1fb2a41
to
9113ea3
Compare
Oh lol the extra one I skimmed past. |
let remotepubkey = self.pubkeys().payment_point; | ||
let witness_script = bitcoin::Address::p2pkh(&::bitcoin::PublicKey{compressed: true, inner: remotepubkey}, Network::Testnet).script_pubkey(); | ||
let remotepubkey = bitcoin::PublicKey::new(self.pubkeys().payment_point); | ||
let witness_script = if self.channel_type_features().supports_anchors_zero_fee_htlc_tx() { | ||
chan_utils::get_to_countersignatory_with_anchors_redeemscript(&remotepubkey.inner) | ||
} else { | ||
Script::new_p2pkh(&remotepubkey.pubkey_hash()) | ||
}; | ||
let sighash = hash_to_message!(&sighash::SighashCache::new(spend_tx).segwit_signature_hash(input_idx, &witness_script, descriptor.output.value, EcdsaSighashType::All).unwrap()[..]); | ||
let remotesig = sign_with_aux_rand(secp_ctx, &sighash, &self.payment_key, &self); | ||
let payment_script = bitcoin::Address::p2wpkh(&::bitcoin::PublicKey{compressed: true, inner: remotepubkey}, Network::Bitcoin).unwrap().script_pubkey(); | ||
let payment_script = if self.channel_type_features().supports_anchors_zero_fee_htlc_tx() { | ||
witness_script.to_v0_p2wsh() | ||
} else { | ||
Script::new_v0_p2wpkh(&remotepubkey.wpubkey_hash().unwrap()) | ||
}; | ||
|
||
if payment_script != descriptor.output.script_pubkey { return Err(()); } | ||
|
||
let mut witness = Vec::with_capacity(2); | ||
witness.push(remotesig.serialize_der().to_vec()); | ||
witness[0].push(EcdsaSighashType::All as u8); | ||
witness.push(remotepubkey.serialize().to_vec()); | ||
if self.channel_type_features().supports_anchors_zero_fee_htlc_tx() { | ||
witness.push(witness_script.to_bytes()); | ||
} else { | ||
witness.push(remotepubkey.to_bytes()); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We condition on this quite a bit. Wonder if it would be worth type parameterizing to avoid missing places in the future. Doesn't need to be done now, of course.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mind giving a more concrete suggestion?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will change even further once we have taproot and we need different sighash and signing methods.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking that rather than checking against features, you initialized the signer with a parameterization for the channel type. Then instead of having an increasingly complex condition for each of these three assignments, each has it's own implementation. The strategy pattern, essentially. Though, in practice this parameterization may need to trickle up to ChannelMonitor
and possibly further, so might not be worth it.
Alternatively, could just use an enum as mentioned later in another comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking that rather than checking against features, you initialized the signer with a parameterization for the channel type. Then instead of having an increasingly complex condition for each of these three assignments, each has it's own implementation. The strategy pattern, essentially. Though, in practice this parameterization may need to trickle up to ChannelMonitor and possibly further, so might not be worth it.
This would be quite a big change if we intend to apply it to all other signing methods on InMemorySigner
, since it wouldn't make sense to have it on some but not others.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the idea though, especially as we add taproot and further channel/commitment upgrades.
let sequence = | ||
if descriptor.channel_transaction_parameters.as_ref() | ||
.map(|channel_params| channel_params.channel_type_features.supports_anchors_zero_fee_htlc_tx()) | ||
.unwrap_or(false) | ||
{ | ||
Sequence::from_consensus(1) | ||
} else { | ||
Sequence::ZERO | ||
}; | ||
input.push(TxIn { | ||
previous_output: descriptor.outpoint.into_bitcoin_outpoint(), | ||
script_sig: Script::new(), | ||
sequence: Sequence::ZERO, | ||
sequence, | ||
witness: Witness::new(), | ||
}); | ||
witness_weight += StaticPaymentOutputDescriptor::MAX_WITNESS_LENGTH; | ||
witness_weight += descriptor.max_witness_length(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm... I'd definitely lean towards a separate variant, FWIW. Seems cleaner.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The thought of adding a new variant came from a confusion between StaticOutput
and StaticPaymentOutput
descriptors. Once we upgrade to Taproot, we'd also need a new variant because we're going from P2WSH to P2TR. Not sure it's worth having all these different types when it's still the same output (to_remote
on a counterparty commitment) we're claiming backed by the same key.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. My thinking was that by explicitly enumerating the cases, we would be forced to consider all of them and avoid bugs. I suppose an alternative would be to have a mapping from ChannelTypeFeatures
to an enum with a variant for each type that we could match on.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll see if I can come up with something tomorrow, it'll have to wait for when taproot is added here otherwise. In any case, the best way to avoid bugs here would be test coverage via check_spends!()
😛
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SGTM. A mapping wouldn't require any serialization changes, too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to expand on this based on offline discussion. We'll keep the variants as they are in this PR. This will give us a ChannelTransactionParameters
which has a ChannelTypeFeatures
. Later, we can add something like a to_channel_type
method to ChannelTypeFeatures
for internal use that will give an enum for us to match so as to avoid missing cases.
Using either another variant or a nested enum instead could be error prone as it may go out of sync with ChannelTypeFeatures
.
9113ea3
to
163ad99
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code sounds good.
if let Event::SpendableOutputs { outputs, channel_id } = event { | ||
assert_eq!(outputs.len(), 1); | ||
assert!(vec![chan_b.2, chan_a.2].contains(&channel_id.unwrap())); | ||
let spend_tx = nodes[0].keys_manager.backing.spend_spendable_outputs( | ||
&[&outputs[0]], Vec::new(), Script::new_op_return(&[]), 253, None, &Secp256k1::new(), | ||
).unwrap(); | ||
|
||
check_spends!(spend_tx, revoked_claim_transactions.get(&spend_tx.input[0].previous_output.txid).unwrap()); | ||
if let SpendableOutputDescriptor::StaticPaymentOutput(_) = &outputs[0] { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think one way to harden the test can be to count the StaticPaymentOutput
with num_static_payment_output
and check at the end the right number of this type of descriptor has been found.
While our commitment transactions did use the correct `to_remote` script, the `ChannelMonitor`'s was not as it is tracked separately. This would lead to users never receiving an `Event::SpendableOutputs` with a `StaticPaymentOutput` descriptor to claim the funds. Luckily, any users affected which had channel closures confirmed by a counterparty commitment just need to replay the closing transaction to receive the event.
`to_remote` outputs on commitment transactions with anchor outputs have an additional `1 CSV` constraint on its spending condition, transitioning away from the previous P2WPKH script to a P2WSH. Since our `ChannelMonitor` was never updated to track the proper `to_remote` script on anchor outputs channels, we also missed updating our signer to handle the new script changes.
We were not accounting for the extra byte denoting the number of items in the witness stack.
163ad99
to
5607977
Compare
Had to rebase to use |
Thanks for adding the test! LGTM. Fee free to squash the fixups. |
5607977
to
f464aa9
Compare
While our commitment transactions did use the correct
to_remote
script, theChannelMonitor
's was not as it is tracked separately. This would lead to users never receiving anEvent::SpendableOutputs
with aStaticPaymentOutput
descriptor to claim the funds.Luckily, any users affected which had channel closures confirmed by a counterparty commitment just need to replay the closing transaction to receive the event.